#include "dyntype.h"
#include <string.h>

DynType::DynType(const string& name)
{
  obj_size = 0;
  this->name = name;
}

DynType::~DynType()
{
  delete [] none_rep.buf;
}

size_t DynType::get_size() const
{
  return obj_size;
}

const string& DynType::get_name() const
{
  return name;
}

bool DynType::is_none_rep(const membuf_t& o)
{
  if (none_rep.buf_len != o.buf_len) {
    throw Error(__FILE__, __LINE__, "DynType::is_none_rep: none_rep and o buflen mismatch");
  }

  return memcmp(none_rep.buf, o.buf, o.buf_len) == 0;
}

void DynType::dump() const
{
  this->print(cerr);
}

// ..........................................................

void DynTypeManager::register_dyntype(DynType* dyntype)
{
  if (dyntypes.has_key(dyntype->get_name()) == true) {
    throw Error(__FILE__, __LINE__, 
		"dyntype already registered: " + dyntype->get_name());
  }

  CompoundDynType* cdt = dynamic_cast<CompoundDynType*>(dyntype);
  if (cdt) {
    cdt->setup_none_rep();
  }

  dyntypes.set(dyntype->get_name(), dyntype);
}

DynType* DynTypeManager::get_dyntype(const string& dyntypename)
{
  if (dyntypes.has_key(dyntypename) == false) {
    throw Error(__FILE__, __LINE__, "DynTypeManager::get_dyntype: no such dyntype " + dyntypename);
  }
  
  return *dyntypes.get_ptr(dyntypename);
}

void DynTypeManager::save_dyntype(ostream& out, 
				   const string& dyntypename)
{
  if (dyntypes.has_key(dyntypename) == false) {
    throw Error(__FILE__, __LINE__, "DynTypeManager::save_dyntype: no such dyntype registered: " + dyntypename);
  }
  DynType* dt = *dyntypes.get_ptr(dyntypename);
  dt->save(out);
}

string DynTypeManager::load_and_register_dyntype(istream& in)
{
  string dyntypename; in >> dyntypename;  
  DynType* dt = 0;
  if (dyntypename == "int"
      || dyntypename == "double"
      || dyntypename.find("char[") != string::npos) {
    dt = DynTypeManager::get()->get_dyntype(dyntypename);
  } else {
    dt = new CompoundDynType(dyntypename);
    dt->load(in);
    if (this->dyntypes.has_key(dt->get_name()) == false) {
      this->register_dyntype(dt);
    } else {
      cerr << "type exists: " << dt->get_name() << endl;
      delete dt;
    }
  }
  return dyntypename;
}

static void register_known_dyntypes(DynTypeManager* dyntype_info)
{
  DynType* int_dyntype = new IntegralDynType<int>("int");
  dyntype_info->register_dyntype(int_dyntype);

  DynType* double_dyntype = new IntegralDynType<double>("double");
  dyntype_info->register_dyntype(double_dyntype);

  for (int i = 1; i <= MAX_CHAR_N_LEN; i++) {
    ostringstream s; s << "char[" << i << "]";
    DynType* stringn_dyntype = new CharNDynType(s.str(), i);
    dyntype_info->register_dyntype(stringn_dyntype);
  }
}

DynTypeManager* DynTypeManager::only_one = 0;

DynTypeManager* DynTypeManager::get()
{
  if (only_one == 0) {
    only_one = new DynTypeManager();
    register_known_dyntypes(only_one);
  }

  return only_one;
}

void DynTypeManager::clear()
{
  delete only_one;
  only_one = new DynTypeManager();
  register_known_dyntypes(only_one);
}

// ..................................................................

CharNDynType::CharNDynType(const string& name, size_t size) : DynType(name) 
{
  this->obj_size = size;
  this->none_rep.buf = new char[size];
  memset(this->none_rep.buf, '\0', size);
  this->none_rep.buf_len = size;
}

void CharNDynType::print_obj(ostream& out, const membuf_t& o_buf) 
{
  if (obj_size != o_buf.buf_len) {
    throw Error(__FILE__, __LINE__, "CharNDynType::print_obj: length mismatch");
  }
  out << string((char*)o_buf.buf, o_buf.buf_len);
}

bool CharNDynType::equal_obj(const membuf_t& l_v_buf, const membuf_t& r_v_buf) 
{
  if (obj_size != l_v_buf.buf_len) {
    throw Error(__FILE__, __LINE__, "CharNDynType::equal_obj: length mismatch");
  }
  if (obj_size != r_v_buf.buf_len) {
    throw Error(__FILE__, __LINE__, "CharNDynType::equal_obj: length mismatch");
  }
  return memcmp(l_v_buf.buf, r_v_buf.buf, obj_size) == 0;
}

bool CharNDynType::less_obj(const membuf_t& l_v_buf, const membuf_t& r_v_buf) 
{
  if (obj_size != l_v_buf.buf_len) {
    throw Error(__FILE__, __LINE__, "CharNDynType::less_obj: length mismatch");
  }
  if (obj_size != r_v_buf.buf_len) {
    throw Error(__FILE__, __LINE__, "CharNDynType::less_obj: length mismatch");
  }
  return strncmp(l_v_buf.buf, r_v_buf.buf, obj_size) < 0;
}

void CharNDynType::print(ostream& out) const 
{
  out << name;
}

void CharNDynType::save(ostream& out) const 
{
  out << name;
}

void CharNDynType::load(istream& in)
{
}

// ..................................................................

CompoundDynTypeMember::CompoundDynTypeMember(const string& member_name, 
					     const string& member_dyntypename,
					     size_t cardinality,
					     size_t offset)
{
  this->member_name = member_name;
  this->member_dyntype = DynTypeManager::get()->get_dyntype(member_dyntypename);
  this->cardinality = cardinality;
  this->offset = offset;
}

CompoundDynTypeMember::CompoundDynTypeMember()
{
  this->cardinality = 0;
  this->offset = 0;
}

void CompoundDynTypeMember::print(ostream& out) const
{
  size_t member_size = cardinality * member_dyntype->get_size();
  out << member_name << "(" << member_size << " byte(s))"
      << " :" << member_dyntype->get_name()
      << ":" << cardinality 
      << ":" << offset
      ;
}

void CompoundDynTypeMember::save(ostream& out) const
{
  out << member_name << " " << member_dyntype->get_name() << " " << cardinality << " " << offset;
}

void CompoundDynTypeMember::load(istream& in)
{
  string member_dyntypename;
  in >> this->member_name 
     >> member_dyntypename 
     >> this->cardinality
     >> this->offset;
  this->member_dyntype = DynTypeManager::get()->get_dyntype(member_dyntypename);
}

// ....................................................................

CompoundDynType::CompoundDynType() : DynType("")
{
}

CompoundDynType::CompoundDynType(const string& name) : DynType(name)
{
  this->name = name;
  this->obj_size = 0;
}

size_t CompoundDynType::get_member_offset(const string& m_name,
					  size_t cardinality_index) const
{
  size_t m_index = 0;
  if (this->members.find(m_name, &m_index) == false) {
    throw Error(__FILE__, __LINE__,
		"CompoundDynType::get_offset: no such member: " + m_name);
  }
  const CompoundDynTypeMember* m = &members.get_at(m_index);
  if (cardinality_index >= m->cardinality) {
    throw Error(__FILE__, __LINE__,
		"CompoundDynType::get_offset: req cardinality too high");
  }

  return m->offset + cardinality_index * m->member_dyntype->get_size();
}

DynType*
CompoundDynType::get_member_dyntype(const string& m_name) const
{
  size_t m_index = 0;
  if (this->members.find(m_name, &m_index) == false) {
    throw Error(__FILE__, __LINE__, 
		"CompoundDynType::get_offset: no such member: " + m_name);
  }
  const CompoundDynTypeMember* m = &members.get_at(m_index);
  return m->member_dyntype;
}

size_t CompoundDynType::get_nof_members() const {
  return members.size();
}

const string& CompoundDynType::get_member_name(size_t member_index) const
{
  return members.get_at(member_index).member_name;
}

void CompoundDynType::snprintf_member(char* buf, size_t buf_len,
				   size_t member_index,
				   const membuf_t& v_buf) const
{
  const CompoundDynTypeMember* m = &this->members.get_at(member_index);

  if (m->cardinality > 0) { 
    size_t member_type_size = m->member_dyntype->get_size();
    membuf_t member_buf(v_buf.buf + m->offset, member_type_size);

    ostringstream out;
    m->member_dyntype->print_obj(out, member_buf);
    for (size_t c = 1; c < m->cardinality; c++) {
      out << ",";
      member_buf.buf += member_type_size;
      m->member_dyntype->print_obj(out, member_buf);
    }
    strncpy(buf, out.str().c_str(), buf_len);
  }
}

void CompoundDynType::print_obj(ostream& out, const membuf_t& v_buf)
{
  out << this->name 
      << "(" << this->get_size() << ") byte(s))" << endl;
  for (size_t i = 0; i < this->members.size(); i++) {
    const CompoundDynTypeMember* m = &this->members.get_at(i);
    out << m->member_name << " " << m->offset << ": ";

    if (m->cardinality > 0) { 
      size_t member_type_size = m->member_dyntype->get_size();
      membuf_t member_buf(v_buf.buf + m->offset, member_type_size);
      
      m->member_dyntype->print_obj(out, member_buf);
      for (size_t c = 1; c < m->cardinality; c++) {
	out << ",";
	member_buf.buf += member_type_size;
	m->member_dyntype->print_obj(out, member_buf);
      }
    }
    out << endl;
  }
}

void CompoundDynType::setup_none_rep()
{
  none_rep.buf_len = this->obj_size;
  none_rep.buf = new char[this->obj_size];
  memset(none_rep.buf, '\0', this->obj_size);
  for (size_t i = 0; i < this->members.size(); i++) {
    const CompoundDynTypeMember* m = &this->members.get_at(i);

    if (m->cardinality > 0) { 
      size_t member_type_size = m->member_dyntype->get_size();
      membuf_t member_buf(none_rep.buf + m->offset, member_type_size);
      
      memcpy(member_buf.buf, m->member_dyntype->none_rep.buf, member_type_size);
      for (size_t c = 1; c < m->cardinality; c++) {
	member_buf.buf += member_type_size;
	memcpy(member_buf.buf, m->member_dyntype->none_rep.buf, member_type_size);
      }
    }
  }
}

bool 
CompoundDynType::equal_obj(const membuf_t& l_v_buf, const membuf_t& r_v_buf) 
{
  for (size_t i = 0; i < this->members.size(); i++) {
    const CompoundDynTypeMember* m = &this->members.get_at(i);
    size_t type_size = m->member_dyntype->get_size();
    membuf_t l_member_buf(l_v_buf.buf + m->offset, type_size);
    membuf_t r_member_buf(r_v_buf.buf + m->offset, type_size);

    if (!m->member_dyntype->equal_obj(l_member_buf, r_member_buf)) {
      return false;
    }
    
    if (m->cardinality > 1) {
      for (size_t c = 1; c < m->cardinality; c++) {
	l_member_buf.buf += type_size;
	r_member_buf.buf += type_size;
	if (!m->member_dyntype->equal_obj(l_member_buf, r_member_buf)) {
	  return false;
	}
      }
    }
  }
  return true;
}

bool 
CompoundDynType::less_obj(const membuf_t& l_v_buf, const membuf_t& r_v_buf) 
{
  for (size_t i = 0; i < this->members.size(); i++) {
    const CompoundDynTypeMember* m = &this->members.get_at(i);
    size_t type_size = m->member_dyntype->get_size();
    membuf_t l_member_buf(l_v_buf.buf + m->offset, type_size);
    membuf_t r_member_buf(r_v_buf.buf + m->offset, type_size);

    if (m->member_dyntype->less_obj(l_member_buf, r_member_buf)) {
      return true;
    }
    if (m->member_dyntype->less_obj(r_member_buf, l_member_buf)) {
      return false;
    }
    
    if (m->cardinality > 1) {
      for (size_t c = 1; c < m->cardinality; c++) {
	l_member_buf.buf += type_size;
	r_member_buf.buf += type_size;
	if (m->member_dyntype->less_obj(l_member_buf, r_member_buf)) {
	  return true;
	}
	if (m->member_dyntype->less_obj(r_member_buf, l_member_buf)) {
	  return false;
	}
      }    
    }
  }
  return false;
}

bool CompoundDynType::add_member(const string& member_name,
				 const string& member_type,
				 size_t member_offset,
				 size_t cardinality)
{
  if (members.has_key(member_name)) {
    return false;
  }
  
  members.push_back(member_name,
		    CompoundDynTypeMember(member_name, member_type, 
					  cardinality,
					  member_offset));
  obj_size += cardinality * DynTypeManager::get()->get_dyntype(member_type)->get_size();

  return true;
}

const CompoundDynTypeMember* 
CompoundDynType::get_member(const string& member_name) const
{
  if (members.has_key(member_name)) {
    return 0;
  }

  return &members.get(member_name);
}

void CompoundDynType::print(ostream& out) const
{
  out << "dyntype " << name 
      << " (" << obj_size << " bytes) with members below:" 
      << endl;
  for (size_t i = 0; i < members.size(); i++) {
    members.get_at(i).print(out);
    out << endl;
  }
}

void CompoundDynType::save(ostream& out) const
{
  out << name << endl;
  out << members.size() << endl;
  size_t i = 0;
  for (i = 0; i < members.size() - 1; i++) {
    members.get_at(i).save(out);
    out << endl;
  }
  members.get_at(i).save(out);
}

void CompoundDynType::load(istream& in) {
  size_t members_size = 0;
  in >> members_size;
  for (size_t i = 0; i < members_size; i++) {
    CompoundDynTypeMember mbr;
    mbr.load(in);
    obj_size += mbr.cardinality * mbr.member_dyntype->get_size();
    members.push_back(mbr.member_name, mbr);
  }
}
